//
//  BtcSeriesCoinApi.cpp
//  Test
//
//  Created by xgc on 12/19/17.
//  Copyright © 2017 xgc. All rights reserved.
//

#include "BtcSeriesCoinApi.hpp"
#include "../utils/KeyCrypterScrypt.h"
#include "../common/BtSeed.hpp"
#include "../utils/Common.h"
#include "BtcCreateSignTransactionParams.hpp"
#include "libdevcrypto/Common.h"
#include <bitcoin/bitcoin/math/elliptic_curve.hpp>

string BtcSeriesCoinApi::getAddress(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    uint64_t network = 0;

    network = netparams.getHDprivate();//getHdPrivateNet(coinType, netType, netparams);
    string coinName = netparams.getSymbol();

    const hd_private m(dc, network);
    
    hd_private tmp = m;
    
    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    const hd_public pub = tmp.to_public();
    ec_compressed ecc = pub.point();
    
    ec_public ecp(ecc);

    string address;

    if(coinName == CoinSymbol::ZEC) {

        address = zecAddressFromPublic(ecp, netparams.getP2KH());

    } else {

        payment_address addr(ecp, netparams.getP2KH());
        address = addr.encoded();
    }
    
    return address;
}

string BtcSeriesCoinApi::getPrivate(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    uint64_t network = 0;

    network = netparams.getHDprivate();/*getHdPrivateNet(coinType, netType, netparams)*/
    string coinName = netparams.getSymbol();
    
    const hd_private m(dc, network);
    
    hd_private tmp = m;
    
    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    ec_secret secret = tmp.secret();

    uint16_t  address = netparams.getP2KH();/*getP2kh(coinType, netType, netparams);*/
    uint8_t wif = netparams.getKeyprefixes();/*getKeyPrefixe(coinType, netType, netparams);*/

    uint16_t address_version = 0;

    if(coinName == CoinSymbol::ZEC) {
        address_version = to_zec_version(address, wif);
    }
    else
    {
        address_version = ec_private::to_version(address, wif);
    }
    //uint16_t address_version = ec_private::to_version(address, 239);
    ec_private ecPri(secret, address_version);
    
    return ecPri.encoded();
}

string BtcSeriesCoinApi::getAddressV2(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    uint64_t network = netparams.getHDprivate();
    string coinName = netparams.getSymbol();

    const hd_private m(dc, network);

    hd_private tmp = m;

    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    const hd_public pub = tmp.to_public();
    ec_compressed ecc = pub.point();

    ec_public ecp(ecc);

    string address;

    if(coinName == CoinSymbol::ZEC) {

        address = zecAddressFromPublic(ecp, netparams.getP2KH());

    } else {

        payment_address addr(ecp, netparams.getP2KH());
        address = addr.encoded();
    }

    return address;
}

string BtcSeriesCoinApi::getPrivateV2(data_chunk dc, CoinType coinType, NetworkType netType, vector<ChildNumber> path, NetParams netparams)
{
    uint64_t network = netparams.getHDprivate();
    string coinName = netparams.getSymbol();

    const hd_private m(dc, network);

    hd_private tmp = m;

    int level = path.size();
    for(int i = 0; i < level; i++) {
        ChildNumber cn = path[i];
        if(cn.isHardened()) {
            tmp = tmp.derive_private(cn.num());
        } else {
            tmp = tmp.derive_private(cn.num());
        }
    }

    ec_secret secret = tmp.secret();

    uint16_t  address = netparams.getP2KH();
    uint8_t wif = netparams.getKeyprefixes();

    uint16_t address_version = 0;

    if(coinName == CoinSymbol::ZEC) {
        address_version = to_zec_version(address, wif);
    }
    else
    {
        address_version = ec_private::to_version(address, wif);
    }

    ec_private ecPri(secret, address_version);

    return ecPri.encoded();
}

bool BtcSeriesCoinApi::createWalltByPassword(vector<string> words, string password, vector<ChildNumber> path, NetParams netparams, BtWallet& bw)
{
    const auto seed = decode_mnemonic(words);
    data_chunk dc;
    
    dc = to_chunk(seed);
    
    uint8_t salt[8] = {0};
    KeyCrypterScrypt::randomSalt(salt, 8);
    
    KeyCrypterScrypt kcs;

    uint32_t N = netparams.getN();
    uint32_t R = netparams.getR();
    uint32_t P = netparams.getP();

    if(netparams.getApiVersion() == 0x01) {
        N =32768;
        R = 8;
        P = 1;
    }

    vector<uint8_t> vecIv;
    bool res = kcs.deriveKey(password, salt, 8, N, R, P, vecIv);
    if(!res) {
        return false;
    }

    uint8_t encryptBytes[256] = {0};
    int encryptLen = kcs.encrypt((uint8_t*)&seed[0], seed.size(), encryptBytes, vecIv);
    if(encryptLen <= 0) {
        return false;
    }


    //IV iv;
    //kcs.getIV(iv);
    
    vector<uint8_t> vSeed(encryptBytes, encryptBytes + encryptLen);
    
    EncryptedData encSeedData;
    encSeedData.setIV(vecIv);
    encSeedData.setEncryptedBytes(vSeed);
    
    vector<uint8_t> vMnemonic;
    getMnemonicAsBytes(words, vMnemonic);
    
    encryptLen = kcs.encrypt((uint8_t*)&vMnemonic[0], vMnemonic.size(), encryptBytes, vecIv);
    if(encryptLen <= 0) {
        return false;
    }
    
    vector<uint8_t> vMn(encryptBytes, encryptBytes + encryptLen);
    
    EncryptedData encMnData;
    encMnData.setIV(vecIv);
    encMnData.setEncryptedBytes(vMn);
    
    string hashPwd;
    getSHA256(password, hashPwd);
    
    long creationTimeSeconds = time(NULL);
    
    vector<uint8_t> vSalt(&salt[0], &salt[8]);
    
    BtSeed* btSeed = new BtSeed(dc, words, encMnData, encSeedData, creationTimeSeconds, hashPwd, vSalt);
    bw.setBtSeed(btSeed);
    bw.setSymbol(netparams.getSymbol());
    
    string address;
    if(netparams.getApiVersion() == 0x01) {

        address = getAddress(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }
    else
    {
        address = getAddressV2(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }
    bw.setAddress(address);

    return true;
}

string BtcSeriesCoinApi::getPriKeyFromBtSeed(BtSeed btSeed, string password, vector<ChildNumber> path, NetParams netparams)
{
    KeyCrypterScrypt kcs;
    
    vector<uint8_t> randomSalt = btSeed.getRandomSalt();

    uint32_t N = netparams.getN();
    uint32_t R = netparams.getR();
    uint32_t P = netparams.getP();

    if(netparams.getApiVersion() == 0x01) {
        N =32768;
        R = 8;
        P = 1;
    }

    vector<uint8_t> vecIv;
    bool res = kcs.deriveKey(password, &randomSalt[0], 8, N, R, P, vecIv);
    if(!res) {
        return "";
    }
    
    EncryptedData encSeedData = btSeed.getEncryptedSeed();
    vector<uint8_t> vEncSeed = encSeedData.getEncryptedBytes();
    
    uint8_t plainSeed[1024] = {0};
    
    int len = kcs.decrypt(&vEncSeed[0], vEncSeed.size(), plainSeed, encSeedData.getIV());
    if(len <= 0) {
        return "";
    }
    
    vector<uint8_t> vSeed(&plainSeed[0], &plainSeed[0] + len);
    
    data_chunk dc;
    
    dc = to_chunk(vSeed);

    string strPriv;
    if(netparams.getApiVersion() == 0x01) {
        strPriv = getPrivate(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }
    else
    {
        strPriv = getPrivateV2(dc, netparams.getCoinType(), netparams.getNetType(), path, netparams);
    }

    return strPriv;
}

vector<string> BtcSeriesCoinApi::getMnemonicCodeFromBtSeed(BtSeed btSeed, string password, NetParams netparams)
{
    KeyCrypterScrypt kcs;
    
    vector<string> vRes;
    
    vector<uint8_t> randomSalt = btSeed.getRandomSalt();

    uint32_t N = netparams.getN();
    uint32_t R = netparams.getR();
    uint32_t P = netparams.getP();

    if(netparams.getApiVersion() == 0x01) {
        N =32768;
        R = 8;
        P = 1;
    }

    vector<uint8_t> vecIv;
    bool res = kcs.deriveKey(password, &randomSalt[0], 8, N, R, P, vecIv);
    if(!res) {
        return vRes;
    }
    
    EncryptedData encEncryptedMnemonicCode = btSeed.getEncryptedMnemonicCode();
    vector<uint8_t> vEncMne = encEncryptedMnemonicCode.getEncryptedBytes();
    
    uint8_t plainMne[1024] = {0};
    
    int len = kcs.decrypt(&vEncMne[0], vEncMne.size(), plainMne, encEncryptedMnemonicCode.getIV());
    if(len <= 0) {
        return vRes;
    }
    
    string strMne((char*)plainMne, len);
    
    split(strMne, vRes, " ");
    
    return vRes;
}


bool BtcSeriesCoinApi::createSignTransaction(CreateSignTransactionParams* params, vector<ChildNumber> path, NetParams netparams, map<string, string> & resultMap)
{
    BtcCreateSignTransactionParams* btcParams = (BtcCreateSignTransactionParams*)params;
    string priKey = btcParams->getPriKey();
    NetworkType netType = netparams.getNetType();
    CoinType coinType = netparams.getCoinType();
    string coinName = netparams.getSymbol();
    

    uint16_t address_version = 0;

    uint16_t  address = 0;
    uint8_t wif = 0;


    //address = getP2kh(coinType, netType, netparams);
    //wif = getKeyPrefixe(coinType, netType, netparams);

    address = netparams.getP2KH();
    wif = netparams.getKeyprefixes();

    if(coinName == CoinSymbol::ZEC) {
        address_version = to_zec_version(address, wif);
    }
    else
    {
        address_version = ec_private::to_version(address, wif);
    }
    
    ec_secret secret;
    ec_compressed compressed;
    
    if(priKey.length() == 0)
    {
        string tmpPriKey = getPriKeyFromBtSeed(btcParams->getBtSeed(), btcParams->getPassword(),
                            path, netparams);
        /*
        hd_private hdPri(tmpPriKey, network);
        secret = hdPri.secret();
        hd_public hdpub = hdPri.to_public();
        compressed = hdpub.point();
        */
        ec_private ecPri(tmpPriKey, address_version);
        secret = ecPri.secret();
        ec_public ecPub = ecPri.to_public();
        compressed = ecPub.point();
    }
    else //private key sign
    {
        ec_private ecPri(priKey, address_version);
        secret = ecPri.secret();
        ec_public ecPub = ecPri.to_public();
        compressed = ecPub.point();
        
        /*
        payment_address addr(ecPub, address_version);
        string address = addr.encoded();
        std::cout << address << std::endl;

        string address = zecAddressFromPublic(ecPub, address_version);
        std::cout << address << std::endl;
         */

    }
    
    data_chunk pubkey = to_chunk(compressed);
    
    vector<BtcvoutForm> btcvoutFormList = btcParams->getBtcvoutFormList();
    int size = btcvoutFormList.size();
    
    uint64_t sum = 0;
    transaction tx = transaction();
    tx.set_version(netparams.getVersion());
    
    int estimateSignLen = 0;
    
    for(int i = 0; i < size; i++)
    {
        hash_digest utxoHash;
        decode_hash(utxoHash, btcvoutFormList[i].getHash());
        output_point utxo(utxoHash, btcvoutFormList[i].getN());
        sum += btcvoutFormList[i].getValue();
        
        bool isCoinBase = btcvoutFormList[i].getCoinBase();
        if(isCoinBase) {
            estimateSignLen += 73;
        } else {
            estimateSignLen += 107;
        }
        input input1 = input();
        input1.set_previous_output(utxo);
        input1.set_sequence(0xffffffff);
        input1.value_ = btcvoutFormList[i].getValue();
        
        tx.inputs().push_back(input1);
    }

    short_hash destHash;
    string sendAddress = btcParams->getSendAddress();

    if(coinName != CoinSymbol::ZEC) {
        payment_address destAddress(sendAddress);
        destHash = destAddress.hash();
    }
    else {
        destHash = zecHashFromAddress(sendAddress);
    }


    uint64_t sendValue = btcParams->getSendValue();
    script outputScript = script().to_pay_key_hash_pattern(destHash);
    output output1(sendValue, outputScript);
    tx.outputs().push_back(output1);

    bool recipientsPayFees = btcParams->getRecipientsPayFees();
    uint64_t fee = 0;
    size_t txlen = 0;

    if(recipientsPayFees) {
        txlen = tx.serialized_size() + estimateSignLen;
        fee = txlen * btcParams->getFeePerKb() / 1000;

        sendValue -= fee;
        tx.outputs()[0].set_value(sendValue);
    } else {

        short_hash fromHash;
        string from = btcParams->getFromAddress();

        if(coinName != CoinSymbol::ZEC) {
            payment_address fromAddress(from);
            fromHash = fromAddress.hash();
        }
        else {
            fromHash = zecHashFromAddress(from);
        }

        script outputScript2 = script().to_pay_key_hash_pattern(fromHash);
        output output2(0, outputScript2);
        tx.outputs().push_back(output2);

        txlen = tx.serialized_size() + estimateSignLen;
        fee = txlen * btcParams->getFeePerKb() / 1000;

        uint64_t changeValue = sum  - sendValue - fee;
        tx.outputs()[1].set_value(changeValue);
    }


    uint8_t sign_type = all;
    if(coinType == CoinType::SBTC
            || coinName == CoinSymbol::SBTC) {
        sign_type = fork_sbtc;
        tx.is_sbtc = true;
    }

    if(coinType == CoinType::WBTC
       || coinName == CoinSymbol::WBTC) {
        sign_type = fork_sbtc;
        tx.is_wbtc = true;
    }

    if(coinType == CoinType::BCH
       || coinName == CoinSymbol::BCH) {
        sign_type = fork_sbtc;
        tx.is_bch = true;
    }
    
    for(int j = 0; j < size; j++ )
    {
        endorsement sig;
        script lockingScript;
        
        bool isCoinBase = btcvoutFormList[j].getCoinBase();
        if(isCoinBase) {
            lockingScript = script().to_pay_public_key_pattern(pubkey);
        } else {
            lockingScript = script().to_pay_key_hash_pattern(bitcoin_short_hash(pubkey));
        }
        
        if(lockingScript.create_endorsement(sig, secret, lockingScript, tx, j, sign_type))
        {
            std::cout << "Signature: " << std::endl;
            std::cout << encode_base16(sig) << "\n" << std::endl;
        }
        
        operation::list sigScript;
        sigScript.push_back(operation(sig));
        if(!isCoinBase) {
            sigScript.push_back(operation(pubkey));
        }
        script unlockingScript(sigScript);
        
        tx.inputs()[j].set_script(unlockingScript);
    }
    
    string hex = encode_base16(tx.to_data());
    
    size_t newLen = tx.serialized_size();
    
    std::cout << "Raw Transaction len: " << newLen << std::endl;
    std::cout << "Raw Transaction: " << std::endl;
    std::cout << hex << std::endl;
    
    std::stringstream strIO;
    string strLen;
    strIO << newLen;
    strIO >> strLen;
    strIO.clear();
    
    string strFee;
    strIO << fee;
    strIO >> strFee;
    
    resultMap.insert(make_pair("hex", hex));
    resultMap.insert(make_pair("length", strLen));
    resultMap.insert(make_pair("fee", strFee));
    
    
    return true;
}


bool BtcSeriesCoinApi::validateAddress(const string& address)
{
    payment decoded;
    if (!decode_base58(decoded, address))
        return false;
    
    if((decoded.size() == payment_size) && verify_checksum(decoded))
        return true;
    
    return false;
}

bool BtcSeriesCoinApi::getLbtcRegister(string address, string prikey, string name, NetParams netparams, vector<uint8_t>& encode)
{
    uint16_t address_version = 0;

    uint16_t  veraddress = 0;
    uint8_t wif = 0;

    address = netparams.getP2KH();
    wif = netparams.getKeyprefixes();
    address_version = ec_private::to_version(veraddress, wif);

    ec_secret secret;
    ec_compressed compressed;

    ec_private ecPri(prikey, address_version);
    secret = ecPri.secret();
    ec_public ecPub = ecPri.to_public();
    compressed = ecPub.point();

    data_chunk pubkey = to_chunk(compressed);

    data_chunk data;
    data_sink stream(data);
    ostream_writer sink(stream);

    std::vector<uint8_t> veclogo;
    veclogo.push_back('L');
    veclogo.push_back('B');
    veclogo.push_back('T');
    veclogo.push_back('C');

    int size = veclogo.size();
    sink.write_byte((uint8_t)size);
    sink.write_bytes(&veclogo[0], size);

    size = pubkey.size();
    sink.write_byte((uint8_t)size);
    sink.write_bytes(&pubkey[0], size);

    uint32_t creationTimeSeconds = (uint32_t)time(NULL);
    size = sizeof(creationTimeSeconds);
    sink.write_byte((uint8_t)size);
    sink.write_4_bytes_little_endian(creationTimeSeconds);

    byte_array<4> tmArray = to_little_endian(creationTimeSeconds);
    hash_digest sighash;
    sighash = bitcoin_hash(tmArray);
    data_chunk out;
    ec_signature signature;
    if (!sign(signature, secret, sighash) || !encode_signature(out, signature))
        return false;

    sink.write_byte(out.size());
    sink.write_bytes(out);

    sink.write_byte((uint8_t)0x22);
    sink.write_byte((uint8_t)0xc0);

    size = name.length();
    if(size > 32) {
        return false;
    }
    sink.write_byte((uint8_t)0x20);
    sink.write_bytes((const uint8_t *)(name.data()), size);
    int left = 32 - size;
    if(left > 0) {
        for(int i = 0; i < left; i++) {
            sink.write_byte((uint8_t)0x00);
        }
    }

    stream.flush();

    encode.assign(data.begin(), data.end());

    return true;
}

int  static varSize(uint64_t size)
{
    int ret = 0;

    if (size < 0x4c)
    {
        ret = 1;
    }
    else if (size <= 0xff)
    {
        ret = 2;
    }
    else if (size <= 0xffff)
    {
        ret = 3;
    }
    else
    {
        ret = 5;
    }

    return ret;
}

bool BtcSeriesCoinApi::lbtcVote(string address, string prikey, vector<string> voteAddressList, bool cancelVote, NetParams netparams, vector<uint8_t>& encode)
{
    uint16_t address_version = 0;

    uint16_t  veraddress = 0;
    uint8_t wif = 0;

    address = netparams.getP2KH();
    wif = netparams.getKeyprefixes();
    address_version = ec_private::to_version(veraddress, wif);

    ec_secret secret;
    ec_compressed compressed;

    ec_private ecPri(prikey, address_version);
    secret = ecPri.secret();
    ec_public ecPub = ecPri.to_public();
    compressed = ecPub.point();

    data_chunk pubkey = to_chunk(compressed);

    data_chunk data;
    data_sink stream(data);
    ostream_writer sink(stream);

    std::vector<uint8_t> veclogo;
    veclogo.push_back('L');
    veclogo.push_back('B');
    veclogo.push_back('T');
    veclogo.push_back('C');

    int size = veclogo.size();
    sink.write_byte((uint8_t)size);
    sink.write_bytes(&veclogo[0], size);

    size = pubkey.size();
    sink.write_byte((uint8_t)size);
    sink.write_bytes(&pubkey[0], size);

    uint32_t creationTimeSeconds = (uint32_t)time(NULL);
    size = sizeof(creationTimeSeconds);
    sink.write_byte((uint8_t)size);
    sink.write_4_bytes_little_endian(creationTimeSeconds);

    byte_array<4> tmArray = to_little_endian(creationTimeSeconds);
    hash_digest sighash;
    sighash = bitcoin_hash(tmArray);
    data_chunk out;
    ec_signature signature;
    if (!sign(signature, secret, sighash) || !encode_signature(out, signature))
        return false;

    sink.write_byte(out.size());
    sink.write_bytes(out);

    size = voteAddressList.size();
    if(size <= 0) {
        return false;
    }

    uint64_t addrlen = 0x14 * size;
    uint64_t varsize = varSize(addrlen);

    uint64_t len = 0x14 * size + 1 + varsize;
    varSizeInt(sink, len);
    //sink.write_byte((uint8_t)len);

    if(cancelVote) {
        sink.write_byte((uint8_t)0xc2);
    }
    else
    {
        sink.write_byte((uint8_t)0xc1);
    }

    varSizeInt(sink, addrlen);
    for(int i = 0; i < size; i++) {
        //sink.write_byte((uint8_t)0x14);

        payment_address destAddress(voteAddressList[i]);
        short_hash destHash = destAddress.hash();
        sink.write_short_hash(destHash);
    }

    stream.flush();

    encode.assign(data.begin(), data.end());

    return true;
}

bool BtcSeriesCoinApi::getLbtcVote(string address, string prikey, vector<string> voteAddressList, NetParams netparams, vector<uint8_t>& encode)
{
    return lbtcVote(address, prikey, voteAddressList, false, netparams, encode);
}

bool BtcSeriesCoinApi::getLbtcCancelVote(string address, string prikey, vector<string> voteAddressList, NetParams netparams, vector<uint8_t>& encode)
{
    return lbtcVote(address, prikey, voteAddressList, true, netparams, encode);
}

bool BtcSeriesCoinApi::createLbtcSignTransaction(CreateSignTransactionParams* params, vector<uint8_t> data, vector<ChildNumber> path, NetParams netparams, map<string, string>& resultMap)
{
    BtcCreateSignTransactionParams* btcParams = (BtcCreateSignTransactionParams*)params;
    string priKey = btcParams->getPriKey();
    string coinName = netparams.getSymbol();


    uint16_t address_version = 0;

    uint16_t  address = 0;
    uint8_t wif = 0;


    address = netparams.getP2KH();
    wif = netparams.getKeyprefixes();

    address_version = ec_private::to_version(address, wif);


    ec_secret secret;
    ec_compressed compressed;

    if(priKey.length() == 0)
    {
        string tmpPriKey = getPriKeyFromBtSeed(btcParams->getBtSeed(), btcParams->getPassword(),
                                               path, netparams);

        ec_private ecPri(tmpPriKey, address_version);
        secret = ecPri.secret();
        ec_public ecPub = ecPri.to_public();
        compressed = ecPub.point();
    }
    else //private key sign
    {
        ec_private ecPri(priKey, address_version);
        secret = ecPri.secret();
        ec_public ecPub = ecPri.to_public();
        compressed = ecPub.point();

    }

    data_chunk pubkey = to_chunk(compressed);

    vector<BtcvoutForm> btcvoutFormList = btcParams->getBtcvoutFormList();
    int size = btcvoutFormList.size();

    uint64_t sum = 0;
    transaction tx = transaction();
    tx.set_version(netparams.getVersion());

    int estimateSignLen = 0;

    for(int i = 0; i < size; i++)
    {
        hash_digest utxoHash;
        decode_hash(utxoHash, btcvoutFormList[i].getHash());
        output_point utxo(utxoHash, btcvoutFormList[i].getN());
        sum += btcvoutFormList[i].getValue();

        bool isCoinBase = btcvoutFormList[i].getCoinBase();
        if(isCoinBase) {
            estimateSignLen += 73;
        } else {
            estimateSignLen += 107;
        }
        input input1 = input();
        input1.set_previous_output(utxo);
        input1.set_sequence(0xffffffff);
        input1.value_ = btcvoutFormList[i].getValue();

        tx.inputs().push_back(input1);
    }

    uint64_t fee = 0;
    size_t txlen = 0;

    script outputScript = script().to_pay_null_data_pattern(data);
    output output1(0, outputScript);
    tx.outputs().push_back(output1);

    TxType txType = btcParams->getTxType();
    if(txType == TxType::TX_LBTC_REGISTER) {
        fee = 100000000;
    }
    else
    {
        //txlen = tx.serialized_size() + estimateSignLen;
        //fee = txlen * btcParams->getFeePerKb() / 1000;
        fee += 1000000;
    }

    if(sum < fee)
        return false;

    short_hash fromHash;
    string from = btcParams->getFromAddress();

    payment_address fromAddress(from);
    fromHash = fromAddress.hash();

    script outputScript2 = script().to_pay_key_hash_pattern(fromHash);
    output output2(0, outputScript2);
    tx.outputs().push_back(output2);

    uint64_t changeValue = sum - fee;
    tx.outputs()[1].set_value(changeValue);


    uint8_t sign_type = all;

    for(int j = 0; j < size; j++ )
    {
        endorsement sig;
        script lockingScript;

        bool isCoinBase = btcvoutFormList[j].getCoinBase();
        if(isCoinBase) {
            lockingScript = script().to_pay_public_key_pattern(pubkey);
        } else {
            lockingScript = script().to_pay_key_hash_pattern(bitcoin_short_hash(pubkey));
        }

        if(lockingScript.create_endorsement(sig, secret, lockingScript, tx, j, sign_type))
        {
            std::cout << "Signature: " << std::endl;
            std::cout << encode_base16(sig) << "\n" << std::endl;
        }

        operation::list sigScript;
        sigScript.push_back(operation(sig));
        if(!isCoinBase) {
            sigScript.push_back(operation(pubkey));
        }
        script unlockingScript(sigScript);

        tx.inputs()[j].set_script(unlockingScript);
    }

    string hex = encode_base16(tx.to_data());

    size_t newLen = tx.serialized_size();

    std::cout << "Raw Transaction len: " << newLen << std::endl;
    std::cout << "Raw Transaction: " << std::endl;
    std::cout << hex << std::endl;

    std::stringstream strIO;
    string strLen;
    strIO << newLen;
    strIO >> strLen;
    strIO.clear();

    string strFee;
    strIO << fee;
    strIO >> strFee;

    resultMap.insert(make_pair("hex", hex));
    resultMap.insert(make_pair("length", strLen));
    resultMap.insert(make_pair("fee", strFee));


    return true;
}

